package cn.cas.mango.dbscan;


import cn.cas.mango.util.Pair;
import org.springframework.stereotype.Component;

import java.util.*;
@Component
public class DBSCAN {
    List<Data> allData;
    List<Cluster> clusters;
  // 中心节点的最小阈值为1
    private int minPts = 1;
    private Map<String, List<Data>> neighborsMap;
    private Map<String, Map<String, Double>> map2Score;

    private Map<String, Map<String, Double>> map2ExpandedScore;
    Integer range;
    Map<String, Map<String, Integer>> clusterKeywordMap;
    Map<String, Set<String>> clusterEntitiesPerson;
    Map<String, Set<String>> clusterEntitiesPlace;
    Map<String, Set<String>> clusterEntitiesOrganization;
    Map<String, Set<String>> clusterEntities;
    private Integer maxClusterSize;
    private Pair<Integer, Double> layerThresh;
    private int layer;



    private DBSCAN() {
        this.allData = new ArrayList<>();
        this.clusters = new ArrayList<>();
        this.map2ExpandedScore = new HashMap<>();
    }
    public DBSCAN(List<Data> allData, Integer range,
                  Map<String, Map<String, Integer>> clusterKeywordMap,
                  Map<String, Set<String>> clusterEntitiesPerson,
                  Map<String, Set<String>> clusterEntitiesPlace,
                  Map<String, Set<String>> clusterEntitiesOrganization,
                  Map<String, Set<String>> clusterEntities,
                  Pair<Integer, Double> layerThresh, int layer) {
        this();
        this.allData.addAll(allData);
        this.range = range;
        this.clusterKeywordMap = clusterKeywordMap;
        this.clusterEntitiesPerson = clusterEntitiesPerson;
        this.clusterEntitiesPlace = clusterEntitiesPlace;
        this.clusterEntitiesOrganization = clusterEntitiesOrganization;
        this.clusterEntities = clusterEntities;
        this.layerThresh = layerThresh;
        this.layer = layer;
    }

  public Map<String, Map<String, Double>> getMap2ExpandedScore() {
    return map2ExpandedScore;
  }

  /**
     * 实现dbscan算法
     * @param e，聚合因子
     * @return 返回最终聚合的结果
     */
    public Map<String, List<String>> run(double e, String v, Integer maxClusterSize, Integer minPts,
                                         Map<Integer, Map<String, List<String>>> map2Original) {
      this.maxClusterSize = maxClusterSize;

      if (v == null) {
//        if (e <= 0.1) {
//          minPts = 6;
//        } else if (e <= 0.3) {
//          minPts = 4;
//        } else {
//          minPts = 2;
//        }
        this.minPts = minPts;
      }

      GenerateAllNeighbors generateAllNeighbors = new GenerateAllNeighbors(allData, e, range);
      neighborsMap = generateAllNeighbors.getAll();
      map2Score = generateAllNeighbors.getMap2Score();
      Set<String> allSet = new HashSet<>();
      for (Data data : allData) {
        if (data.isVisited() || data.isAdded()) {
          continue;
        }
        List<Data> neighbors = getNeighbors(data);
        if (neighbors.isEmpty()) {

        } else {
          Cluster cluster = new Cluster(data, layerThresh, layer);
          clusters.add(cluster);
          expand(neighbors, cluster, allSet);
        }
      }
        for (Data data: allData) {
            if (!data.isAdded()) {
                clusters.add(new Cluster(data, layerThresh, layer));
            }
        }
        Collections.sort(clusters);
        for (Cluster cluster: clusters) {
          Map<String, Integer> topKeywords = cluster.getTopKeywords2(map2Original);
          String coreId = cluster.getCore().getId();
          clusterKeywordMap.put(coreId, topKeywords);

          Set<String> entitiesPerson = new LinkedHashSet<>();
          Set<String> entitiesPlace = new LinkedHashSet<>();
          Set<String> entitiesOrganization = new LinkedHashSet<>();
          Set<String> entities = new LinkedHashSet<>();
          cluster.getTopEnt(entitiesPerson, entitiesPlace, entitiesOrganization, entities);
          clusterEntitiesPerson.put(coreId, entitiesPerson);
          clusterEntitiesPlace.put(coreId, entitiesPlace);
          clusterEntitiesOrganization.put(coreId, entitiesOrganization);
          clusterEntities.put(coreId, entities);
        }
        // 返回map2ExpandedScore
        for (Cluster cluster: clusters) {
          Map<Pair<String, String>, Double> expandedEdge = cluster.getExpandedEdge();
          for (Pair<String, String> pair: expandedEdge.keySet()) {
            put2Map2ExpandedScore(expandedEdge, pair);
          }
        }
        return getIds();
    }

    private void put2Map2ExpandedScore(Map<Pair<String, String>, Double> expandedEdge,
                                       Pair<String, String> pair) {
      Double score = expandedEdge.get(pair);
      String data1 = pair.getKey();
      String data2 = pair.getValue();
      if (!map2ExpandedScore.containsKey(data1)) {
        map2ExpandedScore.put(data1, new HashMap<>());
      }
      if (!map2ExpandedScore.containsKey(data2)) {
        map2ExpandedScore.put(data2, new HashMap<>());
      }
      map2ExpandedScore.get(data1).put(data2, score);
      map2ExpandedScore.get(data2).put(data1, score);
    }

    /**
     * 将数据转换成id，方便后序映射
     * @return
     */
    private Map<String, List<String>> getIds() {
        Map<String, List<String>> ret = new LinkedHashMap<>();
        for (Cluster cluster: clusters) {
          String coreId = cluster.getCore().getId();
          ret.put(coreId, cluster.getNeighborsId());
        }
        return ret;
    }

    public Map<String, Map<String, Double>> getMap2Score() {
      return map2Score;
    }

    /**
     * 将第一个数据的邻居，及邻居中“中心节点”的所有邻居加入簇
     * @param neighbors，簇中第一个数据的邻居
     * @param cluster，需要扩张的簇
     */
    void expand(List<Data> neighbors, Cluster cluster, Set<String> allSet) {
      Set<Data> nowNeighbors = new LinkedHashSet<>(neighbors);
      Data core = cluster.getCore();
      for (int i = 0; i < neighbors.size(); i++) {
            Data p = neighbors.get(i);
            if (cluster.size() > maxClusterSize) {
              return;
            }
            if (!p.isAdded()) {
                cluster.add(p);
                // 记录拓展过程
                addExpandedEdge(cluster, allSet, core, p);
            }
            if (!p.isVisited()) {
                p.setVisited();
                List<Data> secondNeighbors = getNeighbors(p);
                if (secondNeighbors.size() >= minPts) {
                    boolean flag = false;
                    for (Data secondNeighbor: secondNeighbors) {
                        if (!secondNeighbor.isAdded() && !nowNeighbors.contains(secondNeighbor)) {
                            neighbors.add(secondNeighbor);
                            nowNeighbors.add(secondNeighbor);
                            // 记录拓展过程
                            addExpandedEdge(cluster, allSet, p, secondNeighbor);
                            flag = true;
                        }
                    }
                    if (flag) {
                        cluster.addCores(p);
                    }
                }
            }

        }
    }

    private void addExpandedEdge(Cluster cluster, Set<String> allSet, Data start, Data end) {
      if (!allSet.contains(end.getId())) {
        cluster.addExpandedEdge(start, end);
        allSet.add(end.getId());
      }
    }

    /**
     *
     * @param data, 输入data
     * @return data的所有邻居
     */
    private List<Data> getNeighbors(Data data) {
      return neighborsMap.get(data.getId());
    }


}
